-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scalafix rename that leads to a name collision breaks compilation #1695
base: main
Are you sure you want to change the base?
Conversation
@@ -126,10 +143,20 @@ object ReplaceSymbolOps { | |||
if sig.name != parent.value => | |||
Patch.empty // do nothing because it was a renamed symbol | |||
case Some(parent) => | |||
val causesCollision = getGlobalImports(ctx.tree).exists { importStmt => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be more performant to get the global imports from the start and manipulate them as patches are processed. We want to handle for foo.Bar => foo.Baz
and bar.Foo => bar.Baz
without breaking compilation, so we have to consider previous renames as well. Now that I think more on it, I guess for completeness we'd also want to handle non-import collisions. Open to suggestions on these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think more on it, I guess for completeness we'd also want to handle non-import collisions. Open to suggestions on these.
Indeed, although this would be more complex as there is not enough information in the tree nor in semanticdb to list all locals for example. You'd have to start a presentation compiler instance (outside the scalafix API), just like ExplicitResultTypes, which would greatly increase code complexity and ease-of-use (as scalafix's Scala binary version must match the target's). Without going that way, there are some heuristics that could prevent non-import collisions (getting visibile members from super traits, getting local val names via the tree, etc), but it's a lot of work to cover them all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be more performant to get the global imports from the start
Indeed - is it possible to call getGlobalImports
once outside the tree traversal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welcome to the project and thanks a lot for putting this together! This feature has a lot of shortcomings, and this does remove one 👍
Don't forget to run scalafixAll
before committing (see CI failure https://github.com/scalacenter/scalafix/actions/runs/3266135538/jobs/5369523474).
@@ -19,6 +21,20 @@ object ReplaceSymbolOps { | |||
case _ => None | |||
} | |||
} | |||
private def extractImports(stats: Seq[Stat]): Seq[Import] = { | |||
stats | |||
.takeWhile(_.is[Import]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this useful?
@@ -126,10 +143,20 @@ object ReplaceSymbolOps { | |||
if sig.name != parent.value => | |||
Patch.empty // do nothing because it was a renamed symbol | |||
case Some(parent) => | |||
val causesCollision = getGlobalImports(ctx.tree).exists { importStmt => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think more on it, I guess for completeness we'd also want to handle non-import collisions. Open to suggestions on these.
Indeed, although this would be more complex as there is not enough information in the tree nor in semanticdb to list all locals for example. You'd have to start a presentation compiler instance (outside the scalafix API), just like ExplicitResultTypes, which would greatly increase code complexity and ease-of-use (as scalafix's Scala binary version must match the target's). Without going that way, there are some heuristics that could prevent non-import collisions (getting visibile members from super traits, getting local val names via the tree, etc), but it's a lot of work to cover them all.
importStmt.importers.flatMap(_.importees).exists { | ||
case Importee.Name(name) => name.value == to.signature.name | ||
case Importee.Rename(_, rename) => rename.value == to.signature.name | ||
case _ => false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wildcards can also cause collisions (although we can't tell using Scalafix APIs). This is probably outside the scope of this PR, but we could have an opt-in "safe" flag that disable global imports whenever it finds wildcard imports.
.collect { case i: Import => i } | ||
} | ||
|
||
@tailrec private final def getGlobalImports(ast: Tree): Seq[Import] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be more granular and collect relevant scoped imports, all the way to the global ones at the top
@@ -126,10 +143,20 @@ object ReplaceSymbolOps { | |||
if sig.name != parent.value => | |||
Patch.empty // do nothing because it was a renamed symbol | |||
case Some(parent) => | |||
val causesCollision = getGlobalImports(ctx.tree).exists { importStmt => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be more performant to get the global imports from the start
Indeed - is it possible to call getGlobalImports
once outside the tree traversal?
@jmcrawford45 will you have time to pursue this? |
@jmcrawford45 I would love to get that in, any chance you could address the outstanding comments? Thanks! |
Fixes #1305